// Copyright (C) 2020  Matthew "strager" Glazar
// See end of file for extended copyright information.

#include <cstdio>
#include <cstdlib>
#include <quick-lint-js/cli/arg-parser.h>
#include <quick-lint-js/cli/cli-location.h>
#include <quick-lint-js/container/concat.h>
#include <quick-lint-js/container/hash-map.h>
#include <quick-lint-js/container/linked-vector.h>
#include <quick-lint-js/container/padded-string.h>
#include <quick-lint-js/container/string-view.h>
#include <quick-lint-js/container/vector.h>
#include <quick-lint-js/generated-source.h>
#include <quick-lint-js/io/file.h>
#include <quick-lint-js/io/output-stream.h>
#include <quick-lint-js/port/char8.h>
#include <quick-lint-js/port/span.h>
#include <quick-lint-js/port/unreachable.h>
#include <quick-lint-js/port/warning.h>
#include <quick-lint-js/reflection/cxx-parser.h>
#include <quick-lint-js/util/algorithm.h>
#include <quick-lint-js/util/cpp.h>
#include <string_view>

QLJS_WARNING_IGNORE_GCC("-Wshadow=compatible-local")

using namespace std::literals::string_view_literals;

namespace quick_lint_js {
namespace {
void write_file_generated_comment(Output_Stream& out) {
  out.append_literal(
      u8R"(// Code generated by tools/generate-trace-sources.cpp. DO NOT EDIT.
// source: src/quick-lint-js/logging/trace-types.h
)"_sv);
}

struct Parsed_Type_Alias {
  String8_View cxx_name;
  String8_View ctf_name;
  String8_View cxx_type;
};

struct Parsed_Enum_Member {
  String8_View cxx_name;
  std::uint64_t value;
};

struct Parsed_Enum {
  String8_View cxx_name;
  String8_View ctf_name;
  String8_View underlying_cxx_type;
  Span<Parsed_Enum_Member> members;
};

struct Parsed_Struct_Member {
  String8_View cxx_type;
  String8_View cxx_name;
  String8_View ctf_size_name;

  // If true, 'type' is the element type of the array.
  bool type_is_array = false;

  bool type_is_zero_terminated = false;
};

struct Parsed_Struct {
  String8_View cxx_name;
  String8_View ctf_name;
  std::optional<std::uint64_t> id;
  Span<Parsed_Struct_Member> members;
  Span<String8_View> template_parameters;
};

enum class Declaration_Kind {
  _enum,
  _struct,
  type_alias,
};

struct Parsed_Declaration {
  Declaration_Kind kind;
  union {
    Parsed_Type_Alias type_alias;
    Parsed_Enum _enum;
    Parsed_Struct _struct;
  };
};

class CXX_Trace_Types_Parser : public CXX_Parser_Base {
 public:
  using CXX_Parser_Base::CXX_Parser_Base;

  void parse_file() {
    this->skip_preprocessor_directives();
    this->expect_skip(u8"namespace");
    this->expect_skip(u8"quick_lint_js");
    this->expect_skip(CXX_Token_Type::left_curly);

    while (!this->peek_is(CXX_Token_Type::right_curly)) {
      if (this->peek_is(u8"struct"_sv) || this->peek_is(u8"template"_sv)) {
        // struct Trace_Foo { };
        this->parse_struct();
      } else if (this->peek_is(u8"enum"_sv)) {
        // enum class Foo : std::uint8_t { };
        this->parse_enum();
      } else if (this->peek_is(u8"using"_sv)) {
        // using A = B;
        this->parse_type_alias();
      } else if (this->peek_is(u8"inline"_sv)) {
        // inline constexpr int x = 42;
        this->skip();
        this->expect_skip(u8"constexpr"_sv);
        while (!this->peek_is(CXX_Token_Type::semicolon)) {
          this->skip();
        }
        this->skip();
      } else {
        this->fatal("expected enum or struct");
      }
    }

    this->expect_skip(CXX_Token_Type::right_curly);
  }

  // struct Trace_Foo { };
  // template <class String16> struct Trace_Foo { };
  void parse_struct() {
    Parsed_Declaration& declaration =
        this->declarations.emplace_back(Parsed_Declaration{
            .kind = Declaration_Kind::_struct,
            ._struct = {},
        });
    Parsed_Struct& s = declaration._struct;

    this->expect(CXX_Token_Type::identifier);

    if (this->peek_is(u8"template"_sv)) {
      // template <class Foo, class Bar>
      this->skip();
      this->expect_skip(CXX_Token_Type::less);
      this->expect_skip(u8"class"_sv);
      Vector<String8_View> template_parameters("template_parameters",
                                               &this->memory_);
      template_parameters.emplace_back(this->expect_skip_identifier());
      this->expect_skip(CXX_Token_Type::greater);
      s.template_parameters = template_parameters.release_to_span();
    }

    this->expect_skip(u8"struct"_sv);

    s.cxx_name = this->expect_skip_identifier();
    s.ctf_name = this->cxx_name_to_ctf_name(s.cxx_name);

    this->expect_skip(CXX_Token_Type::left_curly);
    Vector<Parsed_Struct_Member> members("members", &this->memory_);
    while (!this->peek_is(CXX_Token_Type::right_curly)) {
      if (this->peek_is(u8"static"_sv)) {
        // static constexpr std::uint8_t id = 0x03;
        this->skip();
        this->expect_skip(u8"constexpr"_sv);
        this->expect_skip(u8"std"_sv);
        this->expect_skip(CXX_Token_Type::colon_colon);
        this->expect_skip(u8"uint8_t"_sv);
        this->expect_skip(u8"id"_sv);
        this->expect_skip(CXX_Token_Type::equal);
        this->expect(CXX_Token_Type::number_literal);
        s.id = this->peek().decoded_number;
        this->skip();
        this->expect_skip(CXX_Token_Type::semicolon);
      } else if (this->peek_is(u8"friend"_sv)) {
        // friend bool operator==(...) { ... }
        // friend bool operator==(...);
        this->skip();
        while (!this->peek_is(CXX_Token_Type::right_paren)) {
          this->skip();
        }
        this->skip();
        if (this->peek_is(CXX_Token_Type::semicolon)) {
          // friend bool operator==(...);
          this->skip();
        } else {
          // friend bool operator==(...) { ... }
          while (!this->peek_is(CXX_Token_Type::right_curly)) {
            this->skip();
          }
          this->skip();
        }
      } else {
        // std::uint64_t timestamp;
        // String uri;
        // Span<const Foo> foos;
        // Span<const Foo<String>> foos;
        Parsed_Struct_Member& member = members.emplace_back();

        if (this->peek_is(CXX_Token_Type::left_square)) {
          // [[qljs::trace_ctf_size_name("lsp_documents")]]
          // [[qljs::trace_zero_terminated]]
          this->skip();
          this->expect_skip(CXX_Token_Type::left_square);
          this->expect_skip(u8"qljs"_sv);
          this->expect_skip(CXX_Token_Type::colon_colon);
          String8_View attribute = this->expect_skip_identifier();
          if (attribute == u8"trace_ctf_size_name"_sv) {
            this->expect_skip(CXX_Token_Type::left_paren);
            this->expect(CXX_Token_Type::string_literal);
            member.ctf_size_name = this->peek().decoded_string;
            this->skip();
            this->expect_skip(CXX_Token_Type::right_paren);
          } else if (attribute == u8"trace_zero_terminated"_sv) {
            member.type_is_zero_terminated = true;
          } else {
            this->fatal("unknown attribute");
          }
          this->expect_skip(CXX_Token_Type::right_square);
          this->expect_skip(CXX_Token_Type::right_square);
        }

        if (this->peek_is(u8"Span"_sv)) {
          // Span<const Foo> foos;
          member.type_is_array = true;
          this->skip();
          this->expect_skip(CXX_Token_Type::less);
          this->expect_skip(u8"const"_sv);
        }
        if (!member.type_is_array && !member.ctf_size_name.empty()) {
          this->fatal_at(
              member.ctf_size_name.data(),
              "error: trace_ctf_size_name is only allowed with Span");
        }

        member.cxx_type = this->parse_simple_type_name();

        if (member.type_is_zero_terminated &&
            !(member.cxx_type == u8"String8_View" ||
              member.cxx_type == u8"String16"_sv)) {
          this->fatal_at(
              member.cxx_type.data(),
              "error: trace_zero_terminated is only allowed with string types");
        }

        if (this->peek_is(CXX_Token_Type::less)) {
          // Foo<String> foo;
          this->skip();
          while (!this->peek_is(CXX_Token_Type::greater)) {
            this->skip();
          }
          this->skip();
        }

        if (member.type_is_array) {
          this->expect_skip(CXX_Token_Type::greater);
        }

        member.cxx_name = this->expect_skip_identifier();

        this->expect_skip(CXX_Token_Type::semicolon);
      }
    }
    s.members = members.release_to_span();
    this->expect_skip(CXX_Token_Type::right_curly);
    this->expect_skip(CXX_Token_Type::semicolon);

    if (s.cxx_name == u8"Trace_Context"_sv ||
        s.cxx_name == u8"Trace_Event_Header"_sv) {
      // These classes are special. Parsing and writing is not generated by us.
      this->declarations.pop_back();
    }
  }

  // enum class Foo : std::uint8_t { };
  void parse_enum() {
    Parsed_Declaration& declaration =
        this->declarations.emplace_back(Parsed_Declaration{
            .kind = Declaration_Kind::_enum,
            ._enum = {},
        });
    Parsed_Enum& e = declaration._enum;

    this->expect_skip(u8"enum"_sv);
    this->expect_skip(u8"class"_sv);

    e.cxx_name = this->expect_skip_identifier();
    e.ctf_name = this->cxx_name_to_ctf_name(e.cxx_name);

    this->expect_skip(CXX_Token_Type::colon);

    this->expect_skip(u8"std"_sv);
    this->expect_skip(CXX_Token_Type::colon_colon);
    e.underlying_cxx_type = this->expect_skip_identifier();

    this->expect_skip(CXX_Token_Type::left_curly);
    Vector<Parsed_Enum_Member> members("members", &this->memory_);
    while (!this->peek_is(CXX_Token_Type::right_curly)) {
      // name = 42,
      Parsed_Enum_Member& member = members.emplace_back();

      member.cxx_name = this->expect_skip_identifier();

      this->expect_skip(CXX_Token_Type::equal);

      this->expect(CXX_Token_Type::number_literal);
      member.value = this->peek().decoded_number;
      this->skip();

      this->expect_skip(CXX_Token_Type::comma);
    }
    e.members = members.release_to_span();
    this->expect_skip(CXX_Token_Type::right_curly);
    this->expect_skip(CXX_Token_Type::semicolon);
  }

  // using A = B;
  void parse_type_alias() {
    Parsed_Declaration& declaration =
        this->declarations.emplace_back(Parsed_Declaration{
            .kind = Declaration_Kind::type_alias,
            .type_alias = {},
        });
    Parsed_Type_Alias& type_alias = declaration.type_alias;

    this->expect_skip(u8"using"_sv);

    type_alias.cxx_name = this->expect_skip_identifier();
    type_alias.ctf_name = this->cxx_name_to_ctf_name(type_alias.cxx_name);

    this->expect_skip(CXX_Token_Type::equal);

    type_alias.cxx_type = this->parse_simple_type_name();

    this->expect_skip(CXX_Token_Type::semicolon);
  }

  // std::uint8_t
  // String8_View
  String8_View parse_simple_type_name() {
    if (this->peek_is(u8"std"_sv)) {
      this->skip();
      this->expect_skip(CXX_Token_Type::colon_colon);
    }
    String8_View type_name = this->expect_skip_identifier();
    return type_name;
  }

  const Parsed_Struct& get_struct_with_cxx_name(String8_View cxx_name) {
    for (const Parsed_Declaration& declaration : this->declarations) {
      if (declaration.kind == Declaration_Kind::_struct &&
          declaration._struct.cxx_name == cxx_name) {
        return declaration._struct;
      }
    }
    this->lexer_.fatal_at(cxx_name.data(), "could not find struct");
  }

  const Parsed_Enum* find_enum_with_cxx_name(String8_View cxx_name) {
    for (const Parsed_Declaration& declaration : this->declarations) {
      if (declaration.kind == Declaration_Kind::_enum &&
          declaration._enum.cxx_name == cxx_name) {
        return &declaration._enum;
      }
    }
    return nullptr;
  }

  String8_View resolve_type_aliases_cxx(String8_View cxx_name) {
    for (const Parsed_Declaration& declaration : this->declarations) {
      if (declaration.kind == Declaration_Kind::type_alias &&
          declaration.type_alias.cxx_name == cxx_name) {
        // TODO(strager): Support nested aliases.
        return declaration.type_alias.cxx_type;
      }
    }
    return cxx_name;
  }

  // Trace_Event_Foo_Bar -> foo_bar
  // Trace_Foo_Bar -> foo_bar
  String8_View cxx_name_to_ctf_name(String8_View cxx_name) {
    String8_View ctf_name = this->to_lower(cxx_name);
    ctf_name = remove_prefix_if_present(ctf_name, u8"trace_"_sv);
    ctf_name = remove_prefix_if_present(ctf_name, u8"event_"_sv);
    return ctf_name;
  }

  String8_View to_lower(String8_View s) {
    Span<Char8> lower_s =
        this->memory_.allocate_uninitialized_span<Char8>(s.size());
    std::transform(s.begin(), s.end(), lower_s.begin(), tolower);
    return String8_View(lower_s.data(),
                        narrow_cast<std::size_t>(lower_s.size()));
  }

  Monotonic_Allocator memory_{"CXX_Trace_Types_Parser"};
  Vector<Parsed_Declaration> declarations{"declarations", &this->memory_};
};

void write_010_editor_template(CXX_Trace_Types_Parser& types,
                               Output_Stream& out) {
  write_file_copyright_begin(out);
  write_file_generated_comment(out);

  out.append_literal(
      u8"\n"_sv
      u8"// This file is a template for the 010 Editor. Use it to inspect quick-lint-js\n"_sv
      u8"// trace files. See docs/TRACING.md for more details.\n"_sv
      u8"\n"_sv
      u8"typedef struct {\n"_sv
      u8"    uquad length;\n"_sv
      u8"    if (length > 0) {\n"_sv
      u8"        wchar_t data[length];\n"_sv
      u8"    }\n"_sv
      u8"} utf16le_string <read=read_utf16le_string>;\n"_sv
      u8"\n"_sv
      u8"wstring read_utf16le_string(utf16le_string &s) {\n"_sv
      u8"    if (s.length == 0) {\n"_sv
      u8"        return L\"(empty string)\";\n"_sv
      u8"    } else {\n"_sv
      u8"        return s.data;\n"_sv
      u8"    }\n"_sv
      u8"}\n"_sv
      u8"\n"_sv
      u8"typedef struct {\n"_sv
      u8"    uquad length;\n"_sv
      u8"    if (length > 0) {\n"_sv
      u8"        char data[length];\n"_sv
      u8"    }\n"_sv
      u8"} utf8_string <read=read_utf8_string>;\n"_sv
      u8"\n"_sv
      u8"wstring read_utf8_string(utf8_string &s) {\n"_sv
      u8"    if (s.length == 0) {\n"_sv
      u8"        return \"(empty string)\";\n"_sv
      u8"    } else {\n"_sv
      u8"        return s.data;\n"_sv
      u8"    }\n"_sv
      u8"}\n"_sv
      u8"\n"_sv
      u8"struct Header {\n"_sv
      u8"    uint magic; // 0xc1 0x1f 0xfc 0xc1\n"_sv
      u8"    byte uuid[16];\n"_sv
      u8"    uquad thread_id;\n"_sv
      u8"    ubyte compression_mode;\n"_sv
      u8"};\n"_sv
      u8"\n"_sv);

  Hash_Map<String8_View, String8_View> cxx_type_to_010_type(&types.memory_);
  cxx_type_to_010_type[u8"uint8_t"_sv] = u8"ubyte"_sv;
  cxx_type_to_010_type[u8"uint32_t"_sv] = u8"uint"_sv;
  cxx_type_to_010_type[u8"uint64_t"_sv] = u8"uquad"_sv;
  cxx_type_to_010_type[u8"String8_View"_sv] = u8"utf8_string"_sv;
  cxx_type_to_010_type[u8"String16"_sv] = u8"utf16le_string"_sv;

  out.append_literal(u8"enum <ubyte> Event_Type {\n"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct) {
      const Parsed_Struct& s = declaration._struct;
      if (s.id.has_value()) {
        out.append_literal(u8"    "_sv);
        out.append_copy(s.ctf_name);
        out.append_literal(u8" = 0x"_sv);
        out.append_fixed_hexadecimal_integer(*s.id, 2);
        out.append_literal(u8",\n"_sv);
      }
    }
  }
  out.append_literal(u8"};\n\n"_sv);

  for (const Parsed_Declaration& declaration : types.declarations) {
    switch (declaration.kind) {
    case Declaration_Kind::type_alias: {
      const Parsed_Type_Alias& type_alias = declaration.type_alias;
      out.append_literal(u8"typedef "_sv);
      out.append_copy(cxx_type_to_010_type.at(type_alias.cxx_type));
      out.append_literal(u8" "_sv);
      out.append_copy(type_alias.cxx_name);
      out.append_literal(u8" <format=hex>;\n\n"_sv);
      break;
    }

    case Declaration_Kind::_struct: {
      const Parsed_Struct& s = declaration._struct;
      out.append_literal(u8"struct "_sv);
      out.append_copy(s.cxx_name);
      out.append_literal(u8" {\n"_sv);
      for (const Parsed_Struct_Member& member : s.members) {
        if (member.type_is_array) {
          out.append_literal(u8"    uquad "_sv);
          out.append_copy(member.ctf_size_name);
          out.append_literal(u8";\n"_sv);
        }

        out.append_literal(u8"    "_sv);
        if (member.type_is_zero_terminated) {
          QLJS_ASSERT(member.cxx_type == u8"String8_View"_sv);
          out.append_literal(u8"string"_sv);
        } else {
          auto type_it = cxx_type_to_010_type.find(member.cxx_type);
          if (type_it == cxx_type_to_010_type.end()) {
            out.append_copy(member.cxx_type);
          } else {
            out.append_copy(type_it->second);
          }
        }
        out.append_literal(u8" "_sv);
        out.append_copy(member.cxx_name);
        if (member.type_is_array) {
          out.append_literal(u8"["_sv);
          out.append_copy(member.ctf_size_name);
          out.append_literal(u8"] <optimize=false>"_sv);
        }
        out.append_literal(u8";\n"_sv);
      }
      out.append_literal(u8"};\n\n"_sv);
      break;
    }

    case Declaration_Kind::_enum: {
      const Parsed_Enum& e = declaration._enum;
      out.append_literal(u8"enum <"_sv);
      out.append_copy(cxx_type_to_010_type.at(e.underlying_cxx_type));
      out.append_literal(u8"> "_sv);
      out.append_copy(e.cxx_name);
      out.append_literal(u8" {\n"_sv);
      for (const Parsed_Enum_Member& member : e.members) {
        out.append_literal(u8"    "_sv);
        out.append_copy(member.cxx_name);
        out.append_literal(u8" = "_sv);
        out.append_decimal_integer(member.value);
        out.append_literal(u8",\n"_sv);
      }
      out.append_literal(u8"};\n\n"_sv);
      break;
    }
    }
  }

  out.append_literal(
      u8"typedef struct {\n"_sv
      u8"    uquad timestamp;\n"_sv
      u8"    Event_Type event_id;\n"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct) {
      const Parsed_Struct& s = declaration._struct;
      if (s.id.has_value()) {
        out.append_literal(u8"    if (event_id == "_sv);
        out.append_copy(s.ctf_name);
        out.append_literal(u8") {\n"_sv);
        out.append_literal(u8"        "_sv);
        out.append_copy(s.cxx_name);
        out.append_literal(u8" event <open=true, name=\""_sv);
        out.append_copy(s.cxx_name);
        out.append_literal(u8"\">;\n"_sv);
        out.append_literal(u8"    }\n"_sv);
      }
    }
  }
  out.append_literal(u8"} Event <read=ReadEvent>;\n\n"_sv);

  out.append_literal(
      u8"string ReadEvent(Event &e) {\n"_sv
      u8"    return EnumToString(e.event_id);\n"_sv
      u8"}\n"_sv
      u8"\n"_sv
      u8"LittleEndian();\n"_sv
      u8"Header header;\n"_sv
      u8"while (!FEof()) {\n"_sv
      u8"    Event event;\n"_sv
      u8"}\n"_sv);

  write_file_copyright_end(out);
}

void write_metadata_cpp(CXX_Trace_Types_Parser& types, Output_Stream& out) {
  write_file_generated_comment(out);
  out.append_literal(
      u8R"---(
#include <quick-lint-js/logging/trace-metadata.h>

namespace quick_lint_js {
const Char8 trace_metadata[] =
    u8R"(/* CTF 1.8 */
)---"_sv);

  write_file_copyright_begin(out);

  out.append_literal(
      u8R"(// This file is a Common Trace Format metadata file in the Trace Stream
// Description Language. https://diamon.org/ctf/
//
// This file describes the binary trace files produced by quick-lint-js.

typealias integer { size = 8;  align = 8; signed = false; byte_order = le; } := u8;
typealias integer { size = 16; align = 8; signed = false; byte_order = le; } := u16;
typealias integer { size = 32; align = 8; signed = false; byte_order = le; } := u32;
typealias integer { size = 64; align = 8; signed = false; byte_order = le; } := u64;

typealias string { encoding = utf8; } := utf8_zstring;

// Allows null code points.
typealias struct {
  u64 code_unit_count;
  u16 code_units[code_unit_count];
} := utf16le_string;

typealias struct {
  u64 byte_count;
  u8 bytes[byte_count];
} := utf8_string;

clock {
  name = monotonic_ns_clock;
  freq = 1000000000;
  absolute = false;
};
typealias integer {
  size = 64;
  align = 8;
  signed = false;
  byte_order = le;
  map = clock.monotonic_ns_clock.value;
} := monotonic_ns_timestamp;

trace {
  major = 1;
  minor = 8;
  uuid = "63697571-2d6b-495f-b93e-736a746e696c";
  byte_order = le;
  packet.header := struct {
    u32 magic;
    u8 uuid[16];
  };
};

stream {
  packet.context := struct {
    u64 thread_id;
    u8 compression_scheme;
  };
  event.header := struct {
    monotonic_ns_timestamp timestamp;
    u8 id;
  };
};
)"_sv);

  Hash_Map<String8_View, String8_View> cxx_name_to_ctf_name(&types.memory_);
  cxx_name_to_ctf_name[u8"uint8_t"_sv] = u8"u8"_sv;
  cxx_name_to_ctf_name[u8"uint16_t"_sv] = u8"u16"_sv;
  cxx_name_to_ctf_name[u8"uint32_t"_sv] = u8"u32"_sv;
  cxx_name_to_ctf_name[u8"uint64_t"_sv] = u8"u64"_sv;
  cxx_name_to_ctf_name[u8"String8_View"_sv] = u8"utf8_string"_sv;
  cxx_name_to_ctf_name[u8"String16"_sv] = u8"utf16le_string"_sv;

  auto get_ctf_name = [&](String8_View cxx_name,
                          bool is_zero_terminated = false) {
    QLJS_ASSERT(!cxx_name.empty());
    auto it = cxx_name_to_ctf_name.find(cxx_name);
    if (it == cxx_name_to_ctf_name.end()) {
      types.fatal_at(cxx_name.data(), "error: unknown type: %.*s",
                     narrow_cast<int>(cxx_name.size()),
                     to_string_view(cxx_name).data());
    }
    String8_View ctf_name = it->second;

    if (is_zero_terminated) {
      if (ctf_name == u8"utf8_string"_sv) {
        return u8"utf8_zstring"_sv;
      }
      if (ctf_name == u8"utf16_string"_sv) {
        return u8"utf16_zstring"_sv;
      }
      types.fatal_at(cxx_name.data(),
                     "error: cannot process trace_zero_terminated");
    }

    return ctf_name;
  };

  auto write_struct_member = [&](const Parsed_Struct_Member& member,
                                 String8_View indentation) {
    if (member.type_is_array) {
      out.append_copy(indentation);
      out.append_literal(u8"u64 "_sv);
      if (member.ctf_size_name.empty()) {
        out.append_copy(member.cxx_name);
        out.append_literal(u8"_count"_sv);
      } else {
        out.append_copy(member.ctf_size_name);
      }
      out.append_literal(u8";\n"_sv);
      out.append_copy(indentation);
      out.append_copy(
          get_ctf_name(member.cxx_type,
                       /*is_zero_terminated=*/member.type_is_zero_terminated));
      out.append_literal(u8" "_sv);
      out.append_copy(member.cxx_name);
      out.append_literal(u8"["_sv);
      if (member.ctf_size_name.empty()) {
        out.append_copy(member.cxx_name);
        out.append_literal(u8"_count"_sv);
      } else {
        out.append_copy(member.ctf_size_name);
      }
      out.append_literal(u8"];\n"_sv);
    } else {
      out.append_copy(indentation);
      out.append_copy(
          get_ctf_name(member.cxx_type,
                       /*is_zero_terminated=*/member.type_is_zero_terminated));
      out.append_literal(u8" "_sv);
      out.append_copy(member.cxx_name);
      out.append_literal(u8";\n"_sv);
    }
  };

  for (const Parsed_Declaration& declaration : types.declarations) {
    switch (declaration.kind) {
    case Declaration_Kind::_enum: {
      const Parsed_Enum& e = declaration._enum;
      cxx_name_to_ctf_name[e.cxx_name] = e.ctf_name;

      out.append_literal(u8"\nenum "_sv);
      out.append_copy(e.ctf_name);
      out.append_literal(u8" : "_sv);
      out.append_copy(get_ctf_name(e.underlying_cxx_type));
      out.append_literal(u8" {\n"_sv);
      for (const auto& member : e.members) {
        out.append_literal(u8"  "_sv);
        out.append_copy(member.cxx_name);
        out.append_literal(u8" = "_sv);
        out.append_decimal_integer(member.value);
        out.append_literal(u8",\n"_sv);
      }
      out.append_literal(u8"}\n"_sv);
      break;
    }

    case Declaration_Kind::_struct: {
      const Parsed_Struct& s = declaration._struct;
      cxx_name_to_ctf_name[s.cxx_name] = s.ctf_name;

      bool is_event = s.id.has_value();
      if (is_event) {
        out.append_literal(u8"\nevent {\n  id = "_sv);
        out.append_decimal_integer(*s.id);
        out.append_literal(u8";\n  name = \""_sv);
        out.append_copy(s.ctf_name);
        out.append_literal(u8"\";\n  fields := struct {\n"_sv);
        for (const Parsed_Struct_Member& member : s.members) {
          write_struct_member(member, u8"    "_sv);
        }
        out.append_literal(u8"  };\n};\n"_sv);
      } else {
        out.append_literal(u8"\ntypealias struct {\n"_sv);
        for (const Parsed_Struct_Member& member : s.members) {
          write_struct_member(member, u8"  "_sv);
        }
        out.append_literal(u8"} := "_sv);
        out.append_copy(s.ctf_name);
        out.append_literal(u8";\n"_sv);
      }
      break;
    }

    case Declaration_Kind::type_alias: {
      const Parsed_Type_Alias& type_alias = declaration.type_alias;
      cxx_name_to_ctf_name[type_alias.cxx_name] = type_alias.ctf_name;

      out.append_literal(u8"\ntypealias "_sv);
      out.append_copy(get_ctf_name(type_alias.cxx_type));
      out.append_literal(u8" := "_sv);
      out.append_copy(type_alias.ctf_name);
      out.append_literal(u8";\n"_sv);
      break;
    }
    }
  }

  write_file_copyright_end(out);
  out.append_literal(u8")\";\n}\n"_sv);
}

void write_parser_js(CXX_Trace_Types_Parser& types, Output_Stream& out) {
  write_file_copyright_begin(out);
  write_file_generated_comment(out);
  out.append_literal(
      u8R"(
export class TraceReaderError extends Error {}

export class TraceReaderUnknownEventType extends TraceReaderError {
  constructor() {
    super("unrecognized event type");
  }
}

)"_sv);

  // Value: Name of a TraceByteReader method
  // (src/quick-lint-js/debug/public/trace.mjs)
  Hash_Map<String8_View, String8_View> ctf_name_to_byte_reader_method(
      &types.memory_);
  ctf_name_to_byte_reader_method[u8"u8"] = u8"u8"_sv;
  ctf_name_to_byte_reader_method[u8"u16"] = u8"u16"_sv;
  ctf_name_to_byte_reader_method[u8"u32"] = u8"u32"_sv;
  ctf_name_to_byte_reader_method[u8"u64"] = u8"u64BigInt"_sv;
  ctf_name_to_byte_reader_method[u8"utf8_string"] = u8"utf8String"_sv;
  ctf_name_to_byte_reader_method[u8"utf8_zstring"] = u8"utf8ZString"_sv;
  ctf_name_to_byte_reader_method[u8"utf16le_string"] = u8"utf16String"_sv;

  Hash_Map<String8_View, String8_View> cxx_name_to_ctf_name(&types.memory_);
  cxx_name_to_ctf_name[u8"uint8_t"_sv] = u8"u8"_sv;
  cxx_name_to_ctf_name[u8"uint16_t"_sv] = u8"u16"_sv;
  cxx_name_to_ctf_name[u8"uint32_t"_sv] = u8"u32"_sv;
  cxx_name_to_ctf_name[u8"uint64_t"_sv] = u8"u64"_sv;
  cxx_name_to_ctf_name[u8"String8_View"_sv] = u8"utf8_string"_sv;
  cxx_name_to_ctf_name[u8"String16"_sv] = u8"utf16le_string"_sv;

  auto write_upper_case = [&out](String8_View s) -> void {
    out.append(narrow_cast<int>(s.size()), [&](Char8* o) {
      for (Char8 c : s) {
        *o++ = toupper(c);
      }
      return narrow_cast<int>(s.size());
    });
  };
  auto write_lower_case = [&out](String8_View s) -> void {
    out.append(narrow_cast<int>(s.size()), [&](Char8* o) {
      for (Char8 c : s) {
        *o++ = tolower(c);
      }
      return narrow_cast<int>(s.size());
    });
  };

  auto strings_equal_case_insensitive = [](String8_View lhs,
                                           String8_View rhs) -> bool {
    return ranges_equal(
        lhs, rhs, [](Char8 x, Char8 y) { return tolower(x) == tolower(y); });
  };

  auto write_camel_case = [&](String8_View s, bool start_upper) -> void {
    bool need_upper_case = start_upper;
    while (!s.empty()) {
      std::size_t segment_end = s.find(u8'_');
      String8_View segment;
      if (segment_end == s.npos) {
        segment = s;
        s = String8_View();
      } else {
        segment = s.substr(0, segment_end);
        s = s.substr(segment_end + 1);
      }

      if (!segment.empty()) {
        if (need_upper_case) {
          if (strings_equal_case_insensitive(segment, u8"VSCode"_sv)) {
            out.append_literal(u8"VSCode"_sv);
          } else if (strings_equal_case_insensitive(segment, u8"LSP"_sv)) {
            out.append_literal(u8"LSP"_sv);
          } else if (strings_equal_case_insensitive(segment, u8"ID"_sv)) {
            out.append_literal(u8"ID"_sv);
          } else {
            out.append_copy(toupper(segment[0]));
            write_lower_case(segment.substr(1));
          }
        } else {
          write_lower_case(segment);
        }
      }
      need_upper_case = true;
    }
  };

  auto write_upper_camel_case = [&](String8_View s) -> void {
    write_camel_case(s, /*start_upper=*/true);
  };
  auto write_lower_camel_case = [&](String8_View s) -> void {
    write_camel_case(s, /*start_upper=*/false);
  };

  auto write_field_name = [&](String8_View name) -> void {
    write_lower_camel_case(name);
  };

  auto write_type_name = [&](String8_View name) -> void {
    out.append_literal(u8"Trace"_sv);
    write_upper_camel_case(name);
  };

  auto write_member_read = [&](const Parsed_Struct_Member& member) -> void {
    auto ctf_name_it = cxx_name_to_ctf_name.find(member.cxx_type);
    if (ctf_name_it == cxx_name_to_ctf_name.end()) {
      types.fatal_at(member.cxx_type.data(), "unknown type: %.*s",
                     narrow_cast<int>(member.cxx_type.size()),
                     to_string_view(member.cxx_type).data());
    }
    String8_View ctf_name = ctf_name_it->second;
    if (member.type_is_zero_terminated) {
      if (ctf_name == u8"utf8_string"_sv) {
        ctf_name = u8"utf8_zstring"_sv;
      } else if (ctf_name == u8"utf16_string"_sv) {
        ctf_name = u8"utf16_zstring"_sv;
      } else {
        types.fatal_at(member.cxx_type.data(),
                       "cannot process trace_zero_terminated");
      }
    }

    if (member.type_is_array) {
      out.append_literal(u8"r.sizedArray(() => "_sv);
    }

    auto byte_reader_method_it = ctf_name_to_byte_reader_method.find(ctf_name);
    if (byte_reader_method_it == ctf_name_to_byte_reader_method.end()) {
      write_type_name(ctf_name);
      out.append_literal(u8".parse(r)"_sv);
    } else {
      out.append_literal(u8"r."_sv);
      out.append_copy(byte_reader_method_it->second);
      out.append_literal(u8"()"_sv);
    }

    if (member.type_is_array) {
      out.append_literal(u8")"_sv);
    }
  };

  out.append_literal(u8"export const TraceEventType = {\n"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct &&
        declaration._struct.id.has_value()) {
      out.append_literal(u8"  "_sv);
      write_upper_case(declaration._struct.ctf_name);
      out.append_literal(u8": "_sv);
      out.append_decimal_integer(*declaration._struct.id);
      out.append_literal(u8",\n"_sv);
    }
  }
  out.append_literal(u8"};\n\n"_sv);

  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_enum) {
      const Parsed_Enum& e = declaration._enum;
      cxx_name_to_ctf_name[e.cxx_name] = e.ctf_name;

      out.append_literal(u8"export class TraceReaderInvalid"_sv);
      write_upper_camel_case(e.ctf_name);
      out.append_literal(u8" extends TraceReaderError {\n"_sv);
      out.append_literal(u8"  constructor() {\n"_sv);
      out.append_literal(u8"    super(\"invalid "_sv);
      out.append_copy(e.ctf_name);
      out.append_literal(u8"\");\n"_sv);
      out.append_literal(u8"  }\n"_sv);
      out.append_literal(u8"}\n\n"_sv);

      out.append_literal(u8"export const "_sv);
      write_type_name(declaration._enum.ctf_name);
      out.append_literal(u8" = {\n"_sv);

      for (const Parsed_Enum_Member& member : declaration._enum.members) {
        out.append_literal(u8"  "_sv);
        write_upper_case(member.cxx_name);
        out.append_literal(u8": "_sv);
        out.append_decimal_integer(member.value);
        out.append_literal(u8",\n"_sv);
      }

      out.append_literal(u8"\n"_sv);
      out.append_literal(u8"  parse(r) {\n"_sv);
      out.append_literal(u8"    let value = "_sv);
      Parsed_Struct_Member fake_member;
      fake_member.cxx_type = e.underlying_cxx_type;
      write_member_read(fake_member);
      out.append_literal(u8";\n"_sv);
      out.append_literal(u8"    switch (value) {\n"_sv);
      for (const Parsed_Enum_Member& member : declaration._enum.members) {
        out.append_literal(u8"      case "_sv);
        write_type_name(declaration._enum.ctf_name);
        out.append_literal(u8"."_sv);
        write_upper_case(member.cxx_name);
        out.append_literal(u8":\n"_sv);
      }
      out.append_literal(u8"        return value;\n"_sv);
      out.append_literal(u8"      default:\n"_sv);
      out.append_literal(u8"        throw new TraceReaderInvalid"_sv);
      write_upper_camel_case(e.ctf_name);
      out.append_literal(u8"();\n"_sv);
      out.append_literal(u8"    }\n"_sv);
      out.append_literal(u8"  },\n"_sv);

      out.append_literal(u8"};\n\n"_sv);
    } else if (declaration.kind == Declaration_Kind::_struct &&
               !declaration._struct.id.has_value()) {
      const Parsed_Struct& s = declaration._struct;
      cxx_name_to_ctf_name[s.cxx_name] = s.ctf_name;

      out.append_literal(u8"class "_sv);
      write_type_name(s.ctf_name);
      out.append_literal(u8" {\n"_sv);

      for (const Parsed_Struct_Member& member : declaration._struct.members) {
        out.append_literal(u8"  "_sv);
        write_field_name(member.cxx_name);
        out.append_literal(u8";\n"_sv);
      }
      out.append_literal(u8"\n"_sv);

      out.append_literal(u8"  static parse(r) {\n"_sv);
      out.append_literal(u8"    let result = {}; // TODO(strager): new "_sv);
      write_type_name(s.ctf_name);
      out.append_literal(u8"()\n"_sv);

      for (const Parsed_Struct_Member& member : declaration._struct.members) {
        if (member.type_is_array) {
          out.append_literal(u8"    // prettier-ignore\n"_sv);
        }
        out.append_literal(u8"    result."_sv);
        write_field_name(member.cxx_name);
        out.append_literal(u8" = "_sv);
        write_member_read(member);
        out.append_literal(u8";\n"_sv);
      }

      out.append_literal(u8"    return result;\n"_sv);
      out.append_literal(u8"  }\n"_sv);
      out.append_literal(u8"}\n\n"_sv);
    } else if (declaration.kind == Declaration_Kind::type_alias) {
      const Parsed_Type_Alias& type_alias = declaration.type_alias;
      auto ctf_type_it = cxx_name_to_ctf_name.find(type_alias.cxx_type);
      if (ctf_type_it == cxx_name_to_ctf_name.end()) {
        types.fatal_at(type_alias.cxx_type.data(), "unknown type: %.*s",
                       narrow_cast<int>(type_alias.cxx_type.size()),
                       to_string_view(type_alias.cxx_type).data());
      }
      cxx_name_to_ctf_name[type_alias.cxx_name] = ctf_type_it->second;
    }
  }

  out.append_literal(u8"export function parseEvent(r) {\n"_sv);
  out.append_literal(u8"  let timestamp = r.u64BigInt();\n"_sv);
  out.append_literal(u8"  let eventType = r.u8();\n"_sv);
  out.append_literal(u8"  switch (eventType) {\n"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct &&
        declaration._struct.id.has_value()) {
      const Parsed_Struct& s = declaration._struct;
      out.append_literal(u8"    case TraceEventType."_sv);
      write_upper_case(s.ctf_name);
      out.append_literal(u8":\n"_sv);
      out.append_literal(u8"      return {\n"_sv);
      out.append_literal(u8"        timestamp: timestamp,\n"_sv);
      out.append_literal(u8"        eventType: eventType,\n"_sv);

      for (const Parsed_Struct_Member& member : declaration._struct.members) {
        if (member.type_is_array) {
          out.append_literal(u8"        // prettier-ignore\n"_sv);
        }
        out.append_literal(u8"        "_sv);
        write_field_name(member.cxx_name);
        out.append_literal(u8": "_sv);
        write_member_read(member);
        out.append_literal(u8",\n"_sv);
      }

      out.append_literal(u8"      };\n\n"_sv);
    }
  }

  out.append_literal(u8"    default:\n"_sv);
  out.append_literal(u8"      throw new TraceReaderUnknownEventType();\n"_sv);
  out.append_literal(u8"  }\n"_sv);
  out.append_literal(u8"}\n"_sv);

  write_file_copyright_end(out);
}

void write_struct_reference_for_cxx(Output_Stream& out, const Parsed_Struct& s,
                                    CXX_Trace_Types_Parser& types,
                                    bool use_template_parameters) {
  out.append_copy(s.cxx_name);
  if (!s.template_parameters.empty()) {
    out.append_literal(u8"<"_sv);
    for (String8_View template_parameter : s.template_parameters) {
      if (use_template_parameters) {
        out.append_copy(template_parameter);
      } else {
        if (template_parameter == u8"String16"_sv) {
          out.append_literal(u8"std::u16string_view"_sv);
        } else {
          types.fatal_at(template_parameter.data(), "expected String16");
        }
      }
    }
    out.append_literal(u8">"_sv);
  }
}

void write_struct_reference_for_cxx_parser(Output_Stream& out,
                                           const Parsed_Struct& s,
                                           CXX_Trace_Types_Parser& types) {
  write_struct_reference_for_cxx(out, s, types,
                                 /*use_template_parameters=*/false);
}

void write_struct_reference_for_cxx_writer(Output_Stream& out,
                                           const Parsed_Struct& s,
                                           CXX_Trace_Types_Parser& types) {
  write_struct_reference_for_cxx(out, s, types,
                                 /*use_template_parameters=*/true);
}

void write_parser_h(CXX_Trace_Types_Parser& types, Output_Stream& out) {
  write_file_copyright_begin(out);
  write_file_generated_comment(out);

  out.append_literal(
      u8R"(
#pragma once

#include <quick-lint-js/logging/trace-types.h>
#include <quick-lint-js/port/char8.h>
#include <string_view>

namespace quick_lint_js {
)"_sv);

  out.append_literal(
      u8R"(enum class Parsed_Trace_Event_Type {
  error_invalid_magic,
  error_invalid_uuid,
  error_unsupported_compression_mode,

  packet_header,

)"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_enum) {
      const Parsed_Enum& e = declaration._enum;
      out.append_literal(u8"  error_unsupported_"_sv);
      out.append_copy(e.ctf_name);
      out.append_literal(u8",\n"_sv);
    }
  }
  out.append_literal(u8"\n"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct) {
      const Parsed_Struct& s = declaration._struct;
      if (s.id.has_value()) {
        out.append_literal(u8"  "_sv);
        out.append_copy(s.ctf_name);
        out.append_literal(u8"_event,\n"_sv);
      }
    }
  }
  out.append_literal(
      u8R"(};
)"_sv);

  out.append_literal(
      u8R"(
struct Parsed_Trace_Event {
  Parsed_Trace_Event_Type type;

  Trace_Event_Header header;

  union {
    // 'header' is not initialized for packet_header.
    Trace_Context packet_header;

    // The following have 'header' initialized.
    // clang-format off
)"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct) {
      const Parsed_Struct& s = declaration._struct;
      if (s.id.has_value()) {
        out.append_literal(u8"    "_sv);
        write_struct_reference_for_cxx_parser(out, s, types);
        out.append_literal(u8" "_sv);
        out.append_copy(s.ctf_name);
        out.append_literal(u8"_event;\n"_sv);
      }
    }
  }
  out.append_literal(
      u8R"(    // clang-format on
  };

  // For testing:
  template <class Event>
  Event& get_event();
};

// clang-format off
)"_sv);

  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct) {
      const Parsed_Struct& s = declaration._struct;
      if (s.id.has_value()) {
        out.append_literal(u8"template <>\n"_sv);
        out.append_literal(u8"inline "_sv);
        write_struct_reference_for_cxx_parser(out, s, types);
        out.append_literal(u8"& Parsed_Trace_Event::get_event<"_sv);
        write_struct_reference_for_cxx_parser(out, s, types);
        out.append_literal(u8">() {\n"_sv);
        out.append_literal(u8"  return this->"_sv);
        out.append_copy(s.ctf_name);
        out.append_literal(u8"_event;\n"_sv);
        out.append_literal(u8"}\n"_sv);
      }
    }
  }

  out.append_literal(u8"// clang-format on\n"_sv);
  out.append_literal(u8"}\n"_sv);
  write_file_copyright_end(out);
}

void write_parser_cpp(CXX_Trace_Types_Parser& types, Output_Stream& out) {
  write_file_copyright_begin(out);
  write_file_generated_comment(out);
  out.append_literal(
      u8R"(
#include <cstddef>
#include <quick-lint-js/logging/trace-reader.h>
#include <quick-lint-js/util/binary-reader.h>

// clang-format off

namespace quick_lint_js {
namespace {
)"_sv);

  auto write_parse_expression =
      [&](String8_View cxx_type, bool type_is_array = false,
          bool type_is_zero_terminated = false) -> void {
    cxx_type = types.resolve_type_aliases_cxx(cxx_type);
    if (type_is_array) {
      out.append_literal(u8"self->parse_array<"_sv);
      write_struct_reference_for_cxx_parser(
          out, types.get_struct_with_cxx_name(cxx_type), types);
      out.append_literal(u8">(r, [&]() { return "_sv);
    }
    if (type_is_zero_terminated) {
      if (cxx_type == u8"String8_View"_sv) {
        out.append_literal(u8"self->parse_utf8_zstring(r)"_sv);
      } else {
        types.fatal_at(cxx_type.data(), "cannot process trace_zero_terminated");
      }
    } else {
      if (cxx_type == u8"uint8_t"_sv) {
        out.append_literal(u8"r.u8()"_sv);
      } else if (cxx_type == u8"uint16_t"_sv) {
        out.append_literal(u8"r.u16_le()"_sv);
      } else if (cxx_type == u8"uint32_t"_sv) {
        out.append_literal(u8"r.u32_le()"_sv);
      } else if (cxx_type == u8"uint64_t"_sv) {
        out.append_literal(u8"r.u64_le()"_sv);
      } else if (cxx_type == u8"String8_View"_sv) {
        out.append_literal(u8"self->parse_utf8_string(r)"_sv);
      } else if (cxx_type == u8"String16"_sv) {
        out.append_literal(u8"self->parse_utf16le_string(r)"_sv);
      } else {
        out.append_literal(u8"parse_"_sv);
        out.append_copy(cxx_type);
        out.append_literal(u8"(r, self)"_sv);
      }
    }
    if (type_is_array) {
      out.append_literal(u8"; })"_sv);
    }
  };

  auto write_parse_member = [&](const Parsed_Struct_Member& member) -> void {
    write_parse_expression(member.cxx_type, member.type_is_array,
                           member.type_is_zero_terminated);
  };

  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_enum) {
      const Parsed_Enum& e = declaration._enum;
      out.append_copy(e.cxx_name);
      out.append_literal(u8" parse_"_sv);
      out.append_copy(e.cxx_name);
      out.append_literal(
          u8"(Checked_Binary_Reader& r, Trace_Reader* self) {\n"_sv);
      out.append_literal(u8"  switch ("_sv);
      write_parse_expression(e.underlying_cxx_type);
      out.append_literal(u8") {\n"_sv);
      out.append_literal(u8"  default:\n"_sv);
      out.append_literal(
          u8"    self->on_error(Parsed_Trace_Event_Type::error_unsupported_"_sv);
      out.append_copy(e.ctf_name);
      out.append_literal(u8");\n"_sv);
      out.append_literal(u8"    [[fallthrough]];\n"_sv);
      for (const Parsed_Enum_Member& member : e.members) {
        out.append_literal(u8"  case "_sv);
        out.append_decimal_integer(member.value);
        out.append_literal(u8":\n"_sv);
        out.append_literal(u8"    return "_sv);
        out.append_copy(e.cxx_name);
        out.append_literal(u8"::"_sv);
        out.append_copy(member.cxx_name);
        out.append_literal(u8";\n"_sv);
      }
      out.append_literal(u8"  }\n"_sv);
      out.append_literal(u8"}\n"_sv);
      out.append_literal(u8"\n"_sv);
    } else if (declaration.kind == Declaration_Kind::_struct &&
               !declaration._struct.id.has_value()) {
      const Parsed_Struct& s = declaration._struct;

      write_struct_reference_for_cxx_parser(out, s, types);
      out.append_literal(u8" parse_"_sv);
      out.append_copy(s.cxx_name);
      out.append_literal(
          u8"(Checked_Binary_Reader& r, [[maybe_unused]] Trace_Reader* self) {\n"_sv);
      out.append_literal(u8"  return "_sv);
      write_struct_reference_for_cxx_parser(out, s, types);
      out.append_literal(u8"{\n"_sv);

      for (const Parsed_Struct_Member& member : s.members) {
        out.append_literal(u8"      ."_sv);
        out.append_copy(member.cxx_name);
        out.append_literal(u8" = "_sv);
        write_parse_member(member);
        out.append_literal(u8",\n"_sv);
      }

      out.append_literal(u8"  };\n"_sv);
      out.append_literal(u8"}\n"_sv);
      out.append_literal(u8"\n"_sv);
    }
  }

  out.append_literal(
      u8R"(}

void Trace_Reader::parse_event(Checked_Binary_Reader& r) {
  Trace_Reader* self = this;
  Trace_Event_Header header = {
      .timestamp = r.u64_le(),
  };
  std::uint8_t event_id = r.u8();
  switch (event_id) {
)");
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct &&
        declaration._struct.id.has_value()) {
      const Parsed_Struct& s = declaration._struct;
      out.append_literal(u8"  case 0x"_sv);
      out.append_fixed_hexadecimal_integer(*s.id, 2);
      out.append_literal(u8":\n"_sv);
      out.append_literal(
          u8"    this->parsed_events_.push_back(Parsed_Trace_Event{\n"_sv);
      out.append_literal(u8"        .type = Parsed_Trace_Event_Type::"_sv);
      out.append_copy(s.ctf_name);
      out.append_literal(u8"_event,\n"_sv);
      out.append_literal(u8"        .header = header,\n"_sv);
      out.append_literal(u8"        ."_sv);
      out.append_copy(s.ctf_name);
      out.append_literal(u8"_event =\n"_sv);
      out.append_literal(u8"            "_sv);
      write_struct_reference_for_cxx_parser(out, s, types);
      out.append_literal(u8"{\n"_sv);

      for (const Parsed_Struct_Member& member : s.members) {
        out.append_literal(u8"              ."_sv);
        out.append_copy(member.cxx_name);
        out.append_literal(u8" = "_sv);
        write_parse_member(member);
        out.append_literal(u8",\n"_sv);
      }

      out.append_literal(u8"            },\n"_sv);
      out.append_literal(u8"    });\n"_sv);
      out.append_literal(u8"    break;\n"_sv);
    }
  }

  out.append_literal(
      u8R"(
  default:
    // TODO(strager): Report an error.
    return;
  }
}
}
)"_sv);

  write_file_copyright_end(out);
}

void write_writer_h(CXX_Trace_Types_Parser& types, Output_Stream& out) {
  write_file_copyright_begin(out);
  write_file_generated_comment(out);

  out.append_literal(
      u8R"(
#pragma once

#include <cstdint>
#include <quick-lint-js/container/async-byte-queue.h>
#include <quick-lint-js/logging/trace-types.h>
#include <quick-lint-js/port/char8.h>
#include <quick-lint-js/util/binary-writer.h>
#include <quick-lint-js/util/cast.h>
#include <string_view>

// clang-format off
namespace quick_lint_js {
)"_sv);

  // Write "template <class T>".
  auto write_template_head = [&](Span<const String8_View> template_parameters,
                                 String8_View indentation) {
    if (!template_parameters.empty()) {
      out.append_copy(indentation);
      out.append_literal(u8"template <"_sv);
      bool need_comma = false;
      for (String8_View template_parameter : template_parameters) {
        if (need_comma) {
          out.append_literal(u8", "_sv);
        }
        out.append_literal(u8"class "_sv);
        out.append_copy(template_parameter);
        need_comma = true;
      }
      out.append_literal(u8">\n"_sv);
    }
  };

  out.append_literal(
      u8R"(class Trace_Writer {
 public:
  explicit Trace_Writer(Async_Byte_Queue*);

  // Calls Async_Byte_Queue::commit.
  void commit();

  void write_header(const Trace_Context&);

)"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_struct) {
      const Parsed_Struct& s = declaration._struct;
      if (s.id.has_value()) {
        write_template_head(s.template_parameters, u8"  "_sv);
        out.append_literal(u8"  void write_event(\n"_sv);
        out.append_literal(u8"      const Trace_Event_Header&,\n"_sv);
        out.append_literal(u8"      const "_sv);
        write_struct_reference_for_cxx_writer(out, s, types);
        out.append_literal(u8"&);\n"_sv);
      }
    }
  }

  out.append_literal(u8"\n private:\n"_sv);
  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_enum) {
      // See NOTE[Trace_Writer-enum-code-gen].
    } else if (declaration.kind == Declaration_Kind::_struct) {
      const Parsed_Struct& s = declaration._struct;
      if (!s.id.has_value()) {
        write_template_head(s.template_parameters, u8"  "_sv);
        out.append_literal(u8"  void write_"_sv);
        out.append_copy(s.cxx_name);
        out.append_literal(u8"(\n"_sv);
        out.append_literal(u8"      const "_sv);
        write_struct_reference_for_cxx_writer(out, s, types);
        out.append_literal(u8"&);\n"_sv);
      }
    }
  }

  out.append_literal(
      u8R"(
  template <class Func>
  void append_binary(Async_Byte_Queue::Size_Type size, Func&& callback);

  template <class String>
  void write_utf16le_string(String string);

  void write_utf8_string(String8_View);
  void write_utf8_zstring(String8_View);

  Async_Byte_Queue* out_;
};

)"_sv);

  struct Built_In_Type {
    String8_View write_method;
    int byte_size;
  };
  Hash_Map<String8_View, Built_In_Type> built_in_types(&types.memory_);
  built_in_types[u8"uint8_t"_sv] = Built_In_Type{u8"u8"_sv, 1};
  built_in_types[u8"uint64_t"_sv] = Built_In_Type{u8"u64_le"_sv, 8};

  for (const Parsed_Declaration& declaration : types.declarations) {
    if (declaration.kind == Declaration_Kind::_enum) {
      // See NOTE[Trace_Writer-enum-code-gen].
    } else if (declaration.kind == Declaration_Kind::_struct) {
      const Parsed_Struct& s = declaration._struct;
      write_template_head(s.template_parameters, u8""_sv);
      out.append_literal(u8"inline void Trace_Writer::"_sv);
      if (s.id.has_value()) {
        out.append_literal(u8"write_event"_sv);
      } else {
        out.append_literal(u8"write_"_sv);
        out.append_copy(s.cxx_name);
      }
      out.append_literal(u8"(\n"_sv);
      if (s.id.has_value()) {
        out.append_literal(u8"      const Trace_Event_Header& header,\n"_sv);
      }
      out.append_literal(u8"      const "_sv);
      write_struct_reference_for_cxx_writer(out, s, types);
      out.append_literal(u8"& s) {\n"_sv);

      struct Write {
        int byte_size;  // -1 if write_code does not use a Binary_Writer.
        String8 code;
      };
      std::vector<Write> writes;
      if (s.id.has_value()) {
        writes.push_back(Write{
            .byte_size = 8,
            .code = u8"w.u64_le(header.timestamp);\n",
        });
        writes.push_back(Write{
            .byte_size = 1,
            .code = u8"w.u8(s.id);\n",
        });
      }
      // We perform two passes:
      // Pass #1: Collect write calls.
      for (const Parsed_Struct_Member& member : s.members) {
        if (member.type_is_array) {
          writes.push_back(Write{
              .byte_size = 8,
              .code = concat(u8"w.u64_le(narrow_cast<std::uint64_t>(s."_sv,
                             member.cxx_name, u8".size()));\n"_sv),
          });
        }

        Memory_Output_Stream code;
        String8 var = concat(u8"s."_sv, member.cxx_name);
        int size = -1;
        if (member.type_is_array) {
          code.append_literal(u8"for (const "_sv);
          write_struct_reference_for_cxx_writer(
              code, types.get_struct_with_cxx_name(member.cxx_type), types);
          code.append_literal(u8"& item : s."_sv);
          code.append_copy(member.cxx_name);
          code.append_literal(u8") {\n"_sv);

          var = u8"item"_sv;
        }

        String8_View type = types.resolve_type_aliases_cxx(member.cxx_type);
        bool need_enum_to_int_cast = false;
        if (const Parsed_Enum* e = types.find_enum_with_cxx_name(type)) {
          // NOTE[Trace_Writer-enum-code-gen]: Instead of creating a dedicated
          // function for writing each enum type, we write the enum as its
          // primitive type directly. This allows writing of the enum to be
          // batched into one append_bytes call with other integers.
          need_enum_to_int_cast = true;
          type = e->underlying_cxx_type;
        }
        auto built_in_it = built_in_types.find(type);
        if (built_in_it == built_in_types.end()) {
          if (member.cxx_type == u8"String8_View"_sv) {
            code.append_literal(u8"  "_sv);
            if (member.type_is_zero_terminated) {
              code.append_literal(u8"this->write_utf8_zstring"_sv);
            } else {
              code.append_literal(u8"this->write_utf8_string"_sv);
            }
            code.append_literal(u8"("_sv);
            code.append_copy(var);
            code.append_literal(u8");\n"_sv);
          } else if (member.cxx_type == u8"String16"_sv) {
            QLJS_ASSERT(!member.type_is_zero_terminated);
            code.append_literal(u8"  this->write_utf16le_string"_sv);
            code.append_literal(u8"("_sv);
            code.append_copy(var);
            code.append_literal(u8");\n"_sv);
          } else {
            // TODO(strager): As a run-time optimization, inline write calls
            // for fixed-sized structs such as Trace_VSCode_Document_Range.
            // This will enable more Binary_Writer fusion.
            code.append_literal(u8"  this->write_"_sv);
            code.append_copy(member.cxx_type);
            code.append_literal(u8"("_sv);
            code.append_copy(var);
            code.append_literal(u8");\n"_sv);
          }
        } else {
          size = built_in_it->second.byte_size;
          code.append_literal(u8"w."_sv);
          code.append_copy(built_in_it->second.write_method);
          code.append_literal(u8"("_sv);
          if (need_enum_to_int_cast) {
            code.append_literal(u8"enum_to_int_cast("_sv);
          }
          code.append_copy(var);
          if (need_enum_to_int_cast) {
            code.append_literal(u8")"_sv);
          }
          code.append_literal(u8");\n"_sv);
        }

        if (member.type_is_array) {
          size = -1;
          code.append_literal(u8"}\n"_sv);
        }

        code.flush();
        writes.push_back(Write{
            .byte_size = size,
            .code = std::move(code).get_flushed_string8(),
        });
      }
      // Pass #2: Batch primitive write calls and emit final code.
      bool made_binary_writer = false;
      for (std::size_t i = 0; i < writes.size();) {
        Write& write = writes[i];
        if (write.byte_size == -1) {
          out.append_copy(write.code);
          i += 1;
        } else {
          int batch_total_bytes = 0;
          std::size_t batch_start = i;
          std::size_t batch_end = batch_start;
          for (; batch_end < writes.size(); ++batch_end) {
            if (writes[batch_end].byte_size == -1) {
              break;
            }
            batch_total_bytes += writes[batch_end].byte_size;
          }
          QLJS_ASSERT(batch_total_bytes > 0);
          if (made_binary_writer) {
            out.append_literal(u8"  w = "_sv);
          } else {
            out.append_literal(u8"  Binary_Writer w = "_sv);
            made_binary_writer = true;
          }
          out.append_literal(
              u8"Binary_Writer(reinterpret_cast<std::uint8_t*>(this->out_->append("_sv);
          out.append_decimal_integer(batch_total_bytes);
          out.append_literal(u8")));\n"_sv);
          for (std::size_t j = batch_start; j < batch_end; ++j) {
            out.append_literal(u8"  "_sv);
            out.append_copy(writes[j].code);
          }
          out.append_literal(u8"  /* Done with w. */\n"_sv);
          i = batch_end;
        }
      }

      out.append_literal(u8"}\n\n"_sv);
    }
  }

  out.append_literal(u8"}\n"_sv);
  write_file_copyright_end(out);
}
}
}

int main(int argc, char** argv) {
  using namespace quick_lint_js;

  const char* trace_types_h_path = nullptr;
  const char* output_010_editor_template_path = nullptr;
  const char* output_metadata_cpp_path = nullptr;
  const char* output_parser_cpp_path = nullptr;
  const char* output_parser_h_path = nullptr;
  const char* output_parser_js_path = nullptr;
  const char* output_writer_h_path = nullptr;
  Arg_Parser parser(argc, argv);
  QLJS_ARG_PARSER_LOOP(parser) {
    QLJS_ARGUMENT(const char* argument) {
      std::fprintf(stderr, "error: unexpected argument: %s\n", argument);
      std::exit(2);
    }

    QLJS_OPTION(const char* arg_value, "--trace-types-h"sv) {
      trace_types_h_path = arg_value;
    }

    QLJS_OPTION(const char* arg_value, "--output-010-editor-template"sv) {
      output_010_editor_template_path = arg_value;
    }

    QLJS_OPTION(const char* arg_value, "--output-metadata-cpp"sv) {
      output_metadata_cpp_path = arg_value;
    }

    QLJS_OPTION(const char* arg_value, "--output-parser-cpp"sv) {
      output_parser_cpp_path = arg_value;
    }

    QLJS_OPTION(const char* arg_value, "--output-parser-h"sv) {
      output_parser_h_path = arg_value;
    }

    QLJS_OPTION(const char* arg_value, "--output-parser-js"sv) {
      output_parser_js_path = arg_value;
    }

    QLJS_OPTION(const char* arg_value, "--output-writer-h"sv) {
      output_writer_h_path = arg_value;
    }

    QLJS_UNRECOGNIZED_OPTION(const char* unrecognized) {
      std::fprintf(stderr, "error: unrecognized option: %s\n", unrecognized);
      std::exit(2);
    }
  }
  if (trace_types_h_path == nullptr) {
    std::fprintf(stderr, "error: missing --trace-types-h\n");
    std::exit(2);
  }

  Result<Padded_String, Read_File_IO_Error> trace_types_source =
      read_file(trace_types_h_path);
  if (!trace_types_source.ok()) {
    std::fprintf(stderr, "error: %s\n",
                 trace_types_source.error_to_string().c_str());
    std::exit(1);
  }

  CLI_Locator locator(&*trace_types_source);
  CXX_Trace_Types_Parser cxx_parser(&*trace_types_source, trace_types_h_path,
                                    &locator);
  cxx_parser.parse_file();

  if (output_010_editor_template_path != nullptr) {
    Memory_Output_Stream out;
    write_010_editor_template(cxx_parser, out);
    out.write_file_if_different_or_exit(output_010_editor_template_path);
  }
  if (output_metadata_cpp_path != nullptr) {
    Memory_Output_Stream out;
    write_metadata_cpp(cxx_parser, out);
    out.write_file_if_different_or_exit(output_metadata_cpp_path);
  }
  if (output_parser_cpp_path != nullptr) {
    Memory_Output_Stream out;
    write_parser_cpp(cxx_parser, out);
    out.write_file_if_different_or_exit(output_parser_cpp_path);
  }
  if (output_parser_h_path != nullptr) {
    Memory_Output_Stream out;
    write_parser_h(cxx_parser, out);
    out.write_file_if_different_or_exit(output_parser_h_path);
  }
  if (output_parser_js_path != nullptr) {
    Memory_Output_Stream out;
    write_parser_js(cxx_parser, out);
    out.write_file_if_different_or_exit(output_parser_js_path);
  }
  if (output_writer_h_path != nullptr) {
    Memory_Output_Stream out;
    write_writer_h(cxx_parser, out);
    out.write_file_if_different_or_exit(output_writer_h_path);
  }

  return 0;
}

// quick-lint-js finds bugs in JavaScript programs.
// Copyright (C) 2020  Matthew "strager" Glazar
//
// This file is part of quick-lint-js.
//
// quick-lint-js is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// quick-lint-js is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with quick-lint-js.  If not, see <https://www.gnu.org/licenses/>.
